/*
 * FXGL - JavaFX Game Library. The MIT License (MIT).
 * Copyright (c) AlmasB (almaslvl@gmail.com).
 * See LICENSE for details.
 */

package com.windea.study.kotlin.fxgl.sample.tutorial

import com.almasb.fxgl.app.*
import com.almasb.fxgl.core.math.*
import com.almasb.fxgl.dsl.*
import com.almasb.fxgl.dsl.FXGL.Companion.entityBuilder
import com.almasb.fxgl.dsl.FXGL.Companion.getAppHeight
import com.almasb.fxgl.dsl.FXGL.Companion.getAppWidth
import com.almasb.fxgl.dsl.FXGL.Companion.getGameWorld
import com.almasb.fxgl.dsl.FXGL.Companion.getInput
import com.almasb.fxgl.dsl.FXGL.Companion.spawn
import com.almasb.fxgl.entity.*
import com.almasb.fxgl.input.*
import javafx.geometry.*
import javafx.scene.input.*
import javafx.scene.paint.*
import javafx.scene.shape.*

/**
 * @author Almas Baimagambetov (almaslvl@gmail.com)
 */
class BreakoutApp : GameApplication() {
	private lateinit var paddle1: Entity
	private lateinit var paddle2: Entity
	private lateinit var ball: Entity
	
	override fun initSettings(settings: GameSettings) {
		settings.title = "Breakout"
	}
	
	override fun initInput() {
		getInput().addAction(object : UserAction("Up 1") {
			override fun onAction() {
				paddle1.translateY((-PADDLE_SPEED).toDouble())
			}
		}, KeyCode.W)
		
		getInput().addAction(object : UserAction("Down 1") {
			override fun onAction() {
				paddle1.translateY(PADDLE_SPEED.toDouble())
			}
		}, KeyCode.S)
		
		getInput().addAction(object : UserAction("Up 2") {
			override fun onAction() {
				paddle2.translateY((-PADDLE_SPEED).toDouble())
			}
		}, KeyCode.UP)
		
		getInput().addAction(object : UserAction("Down 2") {
			override fun onAction() {
				paddle2.translateY(PADDLE_SPEED.toDouble())
			}
		}, KeyCode.DOWN)
	}
	
	override fun initGame() {
		getGameWorld().addEntityFactory(BreakoutFactory())
		
		paddle1 = spawn("bat", 0.0, (getAppHeight() / 2 - PADDLE_HEIGHT / 2).toDouble())
		paddle2 = spawn("bat", (getAppWidth() - PADDLE_WIDTH).toDouble(), (getAppHeight() / 2 - PADDLE_HEIGHT / 2).toDouble())
		
		ball = spawn("ball", (getAppWidth() / 2 - BALL_SIZE / 2).toDouble(), (getAppHeight() / 2 - BALL_SIZE / 2).toDouble())
		
		for(i in 0..9) {
			spawn("brick", (getAppWidth() / 2 - 200 - BRICK_WIDTH).toDouble(), (100 + i * (BRICK_HEIGHT + 20)).toDouble())
			spawn("brick", (getAppWidth() / 2 + 200).toDouble(), (100 + i * (BRICK_HEIGHT + 20)).toDouble())
		}
	}
	
	override fun initPhysics() {
		onCollisionBegin(BreakoutType.BALL, BreakoutType.BRICK) { ball, brick ->
			brick.removeFromWorld()
			val velocity = ball.getObject<Point2D>("velocity")
			
			if(FXGLMath.randomBoolean()) {
				ball.setProperty("velocity", Point2D(-velocity.x, velocity.y))
			} else {
				ball.setProperty("velocity", Point2D(velocity.x, -velocity.y))
			}
		}
	}
	
	override fun onUpdate(tpf: Double) {
		val velocity = ball.getObject<Point2D>("velocity")
		ball.translate(velocity)
		
		if(ball.x == paddle1.rightX
		   && ball.y < paddle1.bottomY
		   && ball.bottomY > paddle1.y) {
			ball.setProperty("velocity", Point2D(-velocity.x, velocity.y))
		}
		
		if(ball.rightX == paddle2.x
		   && ball.y < paddle2.bottomY
		   && ball.bottomY > paddle2.y) {
			ball.setProperty("velocity", Point2D(-velocity.x, velocity.y))
		}
		
		if(ball.x <= 0) {
			resetBall()
		}
		
		if(ball.rightX >= getAppWidth()) {
			resetBall()
		}
		
		if(ball.y <= 0) {
			ball.y = 0.0
			ball.setProperty("velocity", Point2D(velocity.x, -velocity.y))
		}
		
		if(ball.bottomY >= getAppHeight()) {
			ball.y = (getAppHeight() - BALL_SIZE).toDouble()
			ball.setProperty("velocity", Point2D(velocity.x, -velocity.y))
		}
	}
	
	private fun resetBall() {
		ball.setPosition((getAppWidth() / 2 - BALL_SIZE / 2).toDouble(), (getAppHeight() / 2 - BALL_SIZE / 2).toDouble())
		ball.setProperty("velocity", Point2D(BALL_SPEED.toDouble(), BALL_SPEED.toDouble()))
	}
	
	private enum class BreakoutType {
		BRICK, BALL
	}
	
	class BreakoutFactory : EntityFactory {
		
		@Spawns("bat")
		fun newBat(data: SpawnData): Entity {
			return entityBuilder()
				.from(data)
				.viewWithBBox(Rectangle(PADDLE_WIDTH.toDouble(), PADDLE_HEIGHT.toDouble()))
				.build()
		}
		
		@Spawns("ball")
		fun newBall(data: SpawnData): Entity {
			return entityBuilder()
				.from(data)
				.type(BreakoutType.BALL)
				.viewWithBBox(Rectangle(BALL_SIZE.toDouble(), BALL_SIZE.toDouble(), Color.BLUE))
				.collidable()
				.with("velocity", Point2D(BALL_SPEED.toDouble(), BALL_SPEED.toDouble()))
				.build()
		}
		
		@Spawns("brick")
		fun newBrick(data: SpawnData): Entity {
			return entityBuilder()
				.from(data)
				.type(BreakoutType.BRICK)
				.viewWithBBox(Rectangle(BRICK_WIDTH.toDouble(), BRICK_HEIGHT.toDouble(), Color.RED))
				.collidable()
				.build()
		}
	}
	
	companion object {
		private const val PADDLE_WIDTH = 30
		private const val PADDLE_HEIGHT = 100
		private const val BRICK_WIDTH = 50
		private const val BRICK_HEIGHT = 25
		private const val BALL_SIZE = 20
		
		private const val PADDLE_SPEED = 5
		private const val BALL_SPEED = 5
	}
}

fun main(args: Array<String>) {
	GameApplication.launch(BreakoutApp::class.java, args)
}
